1 package net.sf.bse;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import java.io.BufferedOutputStream;
27 import java.io.ByteArrayInputStream;
28 import java.io.ByteArrayOutputStream;
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.OutputStream;
33 import java.io.PrintStream;
34 import java.security.KeyFactory;
35 import java.security.MessageDigest;
36 import java.security.PrivateKey;
37 import java.security.Signature;
38 import java.security.spec.PKCS8EncodedKeySpec;
39 import java.util.ArrayList;
40 import java.util.Hashtable;
41 import java.util.Map;
42 import java.util.StringTokenizer;
43
44 import org.bouncycastle.asn1.DERBitString;
45 import org.bouncycastle.asn1.DERConstructedSequence;
46 import org.bouncycastle.asn1.DERInteger;
47 import org.bouncycastle.asn1.DERObjectIdentifier;
48 import org.bouncycastle.asn1.DEROutputStream;
49 import org.bouncycastle.asn1.DERTaggedObject;
50 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
51 import org.bouncycastle.asn1.x509.GeneralName;
52 import org.bouncycastle.asn1.x509.X509Name;
53 import org.bouncycastle.jce.provider.X509CertificateObject;
54
55 /***
56 * Command to sign an Xlet.
57 *
58 * @author Bill Foote (bill.foote@sun.com)
59 * @author Aleksi Peebles (aleksi.peebles@infocast.fi)
60 * @version $Revision: 1.3 $ $Date: 2004/05/06 09:55:18 $
61 */
62 public class SignXlet extends Command
63 {
64 public SignXlet(Map args)
65 {
66 super(args);
67 }
68
69 public void usageMessage(PrintStream out)
70 {
71 out.println(
72 "Command: xlet\n\n" +
73
74 " Signs an MHP Xlet\n\n" +
75
76 " Arguments:\n\n" +
77
78 " certs: Full names of all certificate files in the certificate\n" +
79 " chain, separated by the OS path separator.\n" +
80 " The file names must be in the correct ascending order:\n" +
81 " signing certificate first and root certificate last.\n" +
82 " key: Full name of file containing signing private key\n" +
83 " src: Base directory to copy Xlet files from\n" +
84 " dest: Destination. If this is equal to src, the files will be\n" +
85 " added/modified in this directory. Otherwise,\n" +
86 " a directory with this name will be created and\n" +
87 " if the directory already exists and the optional rm\n" +
88 " argument is not set to \"true\" the command will fail.\n\n" +
89
90 " Plus, optionally:\n\n" +
91
92 " files: Full names of all files to be signed, separated by the\n" +
93 " OS path separator. All other files will not be signed.\n" +
94 " If this argument is left out all files will be signed.\n" +
95 " rm: If set to \"true\", the dest directory will be deleted\n" +
96 " in the case that it already exists. If set to \"false\"\n" +
97 " (or anything else) or left out, the command will fail if\n" +
98 " the dest directory already exists.\n");
99
100 }
101
102 public String[] getRequiredArgs()
103 {
104 return new String[] { "certs:", "key:", "src:", "dest:" };
105 }
106
107 public String[] getOptionalArgs()
108 {
109 return new String[] { "files:", "rm:" };
110 }
111
112 private X509CertificateObject readCert(String file) throws Exception
113 {
114 System.out.println("Reading certificate from " + file);
115 ByteArrayInputStream bis =
116 new ByteArrayInputStream(readBytesFromFile(file));
117 return readX509(bis);
118 }
119
120 private PrivateKey readPrivateKey(String file) throws Exception
121 {
122 System.out.println("Reading private key from " + file);
123 byte[] encoded = readBytesFromFile(file);
124 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
125 KeyFactory fact = KeyFactory.getInstance("RSA", "BC");
126 PrivateKey key = fact.generatePrivate(spec);
127 return key;
128 }
129
130 private void rmMinusR(File f)
131 {
132 if (f.isFile())
133 {
134 f.delete();
135 }
136 else if (f.isDirectory())
137 {
138 String[] contents = f.list();
139 for (int i = 0; i < contents.length; i++)
140 {
141 rmMinusR(new File(f, contents[i]));
142 }
143 f.delete();
144 }
145 }
146
147 /***
148 * Copies the contents of the <code>src</code> directory to the
149 * <code>dest</code> directory and creates a hashfile in all copied
150 * directories. A separate digest is calculated for every file listed in
151 * <code>signFiles</code> (see MHP 1.0.2 spec 12.4.1.4).
152 */
153 private byte[] multiDigestCopyAndHash(String path, File src, File dest,
154 Hashtable signFiles) throws Exception
155 {
156 if (!src.isDirectory())
157 {
158 throw new IOException(src + " isn't a readable directory");
159 }
160 if (dest != null && !dest.mkdirs())
161 {
162 throw new IOException("Couldn't create directory " + dest);
163 }
164
165
166
167 ByteArrayOutputStream hashFile = new ByteArrayOutputStream();
168
169 String[] contents = src.list();
170
171
172
173 hashFile.write(contents.length >> 8);
174 hashFile.write(contents.length & 0xff);
175
176 for (int i = 0; i < contents.length; i++)
177 {
178 File srcF = new File(src, contents[i]);
179 File destF = dest == null ? null : new File(dest, contents[i]);
180
181 System.out.println("Calculating digest for file " + srcF);
182
183 int digestType = 0;
184 if (signFiles.get(buildPath(path, contents[i])) != null ||
185 signFiles.size() == 0 || srcF.isDirectory())
186 {
187 digestType = 1;
188 }
189 hashFile.write(digestType);
190
191
192
193 hashFile.write(0);
194 hashFile.write(1);
195
196 if (contents[i].length() > 0xff)
197 {
198 throw new IOException("Filename too long: " + contents[i]);
199 }
200
201 hashFile.write(contents[i].length());
202
203 for (int j = 0; j < contents[i].length(); j++)
204 {
205
206 hashFile.write(contents[i].charAt(j));
207 }
208
209
210
211 MessageDigest md5 = MessageDigest.getInstance("MD5", "BC");
212 md5.reset();
213
214 if (srcF.isDirectory())
215 {
216 byte[] ba = multiDigestCopyAndHash(
217 buildPath(path, contents[i]), srcF, destF, signFiles);
218 md5.update(ba);
219 hashFile.write(md5.digest());
220 }
221 else if (srcF.isFile())
222 {
223 byte[] ba = readBytesFromFile(srcF.getAbsolutePath());
224 if (dest != null) {
225 FileOutputStream fos = new FileOutputStream(destF);
226 fos.write(ba);
227 fos.close();
228 }
229 if (digestType > 0)
230 {
231 md5.update(ba);
232 hashFile.write(md5.digest());
233 }
234 }
235 else
236 {
237 System.err.println("Ignoring " + srcF);
238 }
239 }
240
241 hashFile.close();
242
243 byte[] hashContents = hashFile.toByteArray();
244
245 File hashF = new File(dest == null ? src : dest, "dvb.hashfile");
246 FileOutputStream fos = new FileOutputStream(hashF);
247 fos.write(hashContents);
248 fos.close();
249 return hashContents;
250 }
251
252 private String buildPath(String path, String file) {
253 if (path == null || path.length() == 0) {
254 return file;
255 }
256 return path + File.separator + file;
257 }
258
259 private void writeCertificate(OutputStream os, X509CertificateObject cert)
260 throws Exception
261 {
262 byte[] encoded = cert.getEncoded();
263
264
265 os.write(encoded.length >> 16);
266 os.write((encoded.length >> 8) & 0xff);
267 os.write(encoded.length & 0xff);
268
269 os.write(encoded);
270 }
271
272 public void run() throws Exception
273 {
274 Hashtable signFiles = new Hashtable();
275 if (getArg("files:") != null)
276 {
277 StringTokenizer st = new StringTokenizer(getArg("files:"),
278 File.pathSeparator);
279 while (st.hasMoreTokens())
280 {
281
282
283 signFiles.put(st.nextToken(), "MD5");
284 }
285 }
286
287 System.out.println("Signing Xlet " + getArg("src:"));
288
289 File src = new File(getArg("src:"));
290 File dest = new File(getArg("dest:"));
291 if (dest.getAbsolutePath().equals(src.getAbsolutePath())) {
292 dest = null;
293 }
294
295 if (dest != null && dest.exists())
296 {
297 if (getArg("rm:") != null &&
298 getArg("rm:").equalsIgnoreCase("true"))
299 {
300 System.out.println("Removing " + dest);
301 rmMinusR(dest);
302 }
303 else
304 {
305 System.out.println(
306 "Command failed: destination directory already exists.\n");
307 return;
308 }
309 }
310
311
312
313 System.out.println("Generating the certificate file");
314
315 ArrayList certFiles = new ArrayList();
316 StringTokenizer st =
317 new StringTokenizer(getArg("certs:"), File.pathSeparator);
318 while (st.hasMoreTokens())
319 {
320 certFiles.add(st.nextToken());
321 }
322
323 File certFile = new File(src, "dvb.certificates.1");
324 OutputStream os =
325 new BufferedOutputStream(new FileOutputStream(certFile));
326
327
328 os.write(certFiles.size() >> 8);
329 os.write(certFiles.size() & 0xff);
330
331 X509CertificateObject leafCert = readCert((String)certFiles.get(0));
332 writeCertificate(os, leafCert);
333 for (int i = 1; i < certFiles.size(); i++)
334 {
335 writeCertificate(os, readCert((String)certFiles.get(i)));
336 }
337
338 os.close();
339
340 System.out.println("Computing hashes and copying tree");
341
342 byte[] hashFile = multiDigestCopyAndHash(null, src, dest, signFiles);
343
344 System.out.println("Generating signature file");
345
346
347
348
349 PrivateKey key = readPrivateKey(getArg("key:"));
350 Signature engine = Signature.getInstance("MD5WITHRSA", "BC");
351 engine.initSign(key);
352 engine.update(hashFile);
353 byte[] signature = engine.sign();
354 DERConstructedSequence sigSeq = new DERConstructedSequence();
355
356
357
358
359 DERConstructedSequence ci = new DERConstructedSequence();
360
361
362
363
364 X509Name nm = (X509Name)leafCert.getIssuerDN();
365 GeneralName gn = new GeneralName(nm);
366 ci.addObject(new DERTaggedObject(1, gn));
367
368
369 DERInteger ser = new DERInteger(leafCert.getSerialNumber());
370 ci.addObject(new DERTaggedObject(2, ser));
371
372
373 AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(ci);
374 sigSeq.addObject(aki);
375
376
377 sigSeq.addObject(new DERObjectIdentifier("1.2.840.113549.2.5"));
378
379
380 sigSeq.addObject(new DERBitString(signature));
381
382
383 File f = new File(dest == null ? src : dest, "dvb.signaturefile.1");
384 os = new BufferedOutputStream(new FileOutputStream(f));
385 DEROutputStream dos = new DEROutputStream(os);
386 dos.writeObject(sigSeq);
387 dos.close();
388
389
390 if (dest != null && !certFile.delete()) {
391 System.err.println(
392 "Could not delete certificate file in source directory: "
393 + certFile.getName());
394 }
395
396 System.out.println();
397 System.out.println("Done!");
398 System.out.println();
399 }
400 }